package com.tangsm.spring.boot.mybatis.plus.common.interceptor;

import com.tangsm.spring.boot.mybatis.plus.common.util.SecurityUtils;
import com.tangsm.spring.boot.mybatis.plus.domain.model.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;


/**
 * <pre>
 * 自定义 MyBatis 拦截器
 * 自动设置 createTime、createBy、updateTime、updateBy的值
 * 拦截 update 操作（添加和修改）
 * </pre>
 *
 * @author tangsm
 * @see <a href="https://blog.csdn.net/minghao0508/article/details/124420953">帮助文档：Mybatis-拦截器Interceptor</a>
 */
@Slf4j
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class MyBatisInterceptor implements Interceptor {

    /**
     * 拦截 update 操作（添加和修改）
     *
     * @param invocation 调用对象
     * @return 继续下一步
     * @throws Throwable 错误
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 映射语句
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

        // SQL命令
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        // 只判断新增和修改
        if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
            // 获取参数对象
            Object parameter = invocation.getArgs()[1];

            // 参数为空返回继续下一步
            if (parameter == null) {
                return invocation.proceed();
            }

            // 非批量操作
            executeInterceptor(parameter, sqlCommandType);
        }

        // 返回继续下一步
        return invocation.proceed();
    }

    /**
     * 执行拦截处理
     *
     * @param parameter      参数
     * @param sqlCommandType SQL命令
     * @throws Throwable 错误
     */
    public void executeInterceptor(Object parameter, SqlCommandType sqlCommandType) throws Throwable {
        // 登录信息
        LoginUser loginUser = SecurityUtils.getLoginUser();

        // 单行单表
        Field[] fields = null;
        // 多行或多表
//        List<Field[]> allFieldList = null;
        Map<Class, Map<String, Field>> allFieldMap = null;

        // 更新与多表操作判断
        if (parameter instanceof MapperMethod.ParamMap) {
            MapperMethod.ParamMap<?> map = (MapperMethod.ParamMap<?>) parameter;
            // et
            if (map.containsKey("et") && Objects.nonNull(map.get("et"))) {
                parameter = map.get("et");
                fields = getAllFields(parameter);
            }
            // 多行操作
            else if (map.containsKey("list") && Objects.nonNull(map.get("list"))) {
                parameter = map.get("list");
                allFieldMap = getMapAllFields(parameter);
            }
            // param1
            else if (map.containsKey("param1") && Objects.nonNull(map.get("param1"))) {
                parameter = map.get("param1");
                fields = getAllFields(parameter);
            }
        }
        // 单表
        else {
            fields = getAllFields(parameter);
        }

        // 单表操作
        if (fields != null) {
            // 遍历所有字段
            Map<String, Field> fieldsMap = new HashMap<>();
            for (Field field : fields) {
                fieldsMap.put(field.getName(), field);
            }
            // 通过名称判断设置参数
            setParameterByName(parameter, sqlCommandType, loginUser, fieldsMap);
        }

        // 多表或多行操作
        if (!CollectionUtils.isEmpty(allFieldMap) && parameter instanceof List) {
            List listParameter = (List) parameter;
            for (Object item : listParameter) {
                setParameterByName(item, sqlCommandType, loginUser, allFieldMap.get(item.getClass()));
            }
        }
    }

    /**
     * 根据注解设置参数
     *
     * @param parameter      参数
     * @param sqlCommandType SQL命令
     * @param loginUser      登录信息
     * @param fieldsMap      字段Map
     * @throws Throwable 异常信息
     */
    private void setParameterByName(Object parameter, SqlCommandType sqlCommandType, LoginUser loginUser, Map<String, Field> fieldsMap) throws Throwable {
        Field field = null;
        // 插入
        if (SqlCommandType.INSERT.equals(sqlCommandType)) {
            if (loginUser != null) {
                // 部门
                if (fieldsMap.containsKey("dept_id")) {
                    field = fieldsMap.get("dept_id");
                    // 允许访问私钥变量，true表示反射对象在使用时应该抑制Java语言的访问检查
                    field.setAccessible(true);

                    if (isNull(field.get(parameter))) {
                        field.set(parameter, loginUser.getDeptId());
                    }
                }
                // 创建人
                if (fieldsMap.containsKey("createBy")) {
                    field = fieldsMap.get("createBy");
                    // 允许访问私钥变量，true表示反射对象在使用时应该抑制Java语言的访问检查
                    field.setAccessible(true);

                    if (isNull(field.get(parameter))) {
                        field.set(parameter, loginUser.getUserName());
                    }
                }
            }
            // 创建时间
            if (fieldsMap.containsKey("createTime")) {
                field = fieldsMap.get("createTime");
                field.setAccessible(true);

                if (isNull(field.get(parameter))) {
                    field.set(parameter, new Date());
                }
            }
        }

        // 修改人
        if (fieldsMap.containsKey("updateBy") && loginUser != null) {
            field = fieldsMap.get("updateBy");
            field.setAccessible(true);
            field.set(parameter, loginUser.getUserName());
        }
        // 修改时间
        if (fieldsMap.containsKey("updateTime")) {
            field = fieldsMap.get("updateTime");
            field.setAccessible(true);
            field.set(parameter, new Date());
        }
    }

    /**
     * 获取类的所有属性，包括父类
     *
     * @param parameter 参数
     * @return 所有属性集合
     */
    private Field[] getAllFields(Object parameter) {
        // 字段集合
        List<Field> fieldList = new ArrayList<>();
        // 获取类
        Class<?> clazz = parameter.getClass();
        while (clazz != null) {
            // 获取类所有字段不包含父类属性
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));

            // 获父类
            clazz = clazz.getSuperclass();
        }

        // 转换数组
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }

    /**
     * 对象集合获取所有对象的属性
     *
     * @param object 对象集合
     * @return 属性集合
     */
    @SuppressWarnings("unchecked")
    private Map<Class, Map<String, Field>> getMapAllFields(Object object) {
        Map<Class, Map<String, Field>> allFieldMap = new HashMap<>();
        if (object instanceof List) {
            Field[] fields;
            for (Object o : (List<Object>) object) {
                if (!allFieldMap.containsKey(o.getClass())) {
                    fields = getAllFields(o);
                    Map<String, Field> fieldsMap = new HashMap<>();
                    for (Field field : fields) {
                        // 通过名称判断设置参数
                        fieldsMap.put(field.getName(), field);
                    }
                    allFieldMap.put(o.getClass(), fieldsMap);
                }
            }
        }
        return allFieldMap;
    }

    /**
     * 对象集合获取所有对象的属性
     *
     * @param object 对象集合
     * @return 属性集合
     */
    @SuppressWarnings("unchecked")
    private List<Field[]> getListAllFields(Object object) {
        List<Field[]> fieldList = new ArrayList<>();
        if (object instanceof List) {
            Field[] fields;
            for (Object o : (List<Object>) object) {
                fields = getAllFields(o);
                fieldList.add(fields);
            }
        }
        return fieldList;
    }

    /**
     * 是否为空
     *
     * @param obj 值
     * @return 是否
     */
    private boolean isNull(Object obj) {
        return Objects.isNull(obj) || "".equals(obj);
    }
}
